using System;
using System.Linq;
using System.Threading.Tasks;
using JustinAngel.MindwaveSensor.WP8.Common;
using Windows.Networking.Proximity;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

namespace JustinAngel.MindwaveSensor.WP8
{
    public class Mindwave : IMindwaveSensor
    {
        private static Mindwave _current;
        /// <summary>
        /// Gets a singleton of the Mindwave Sensor. 
        /// </summary>
        public static IMindwaveSensor Current
        {
            get
            {
                if (_current == null)
                    _current = new Mindwave();

                return _current;
            }
        }

        /// <summary>
        /// Gets the Bluetooth socket used for Mindwave. 
        /// </summary>
        /// <remarks>
        /// WARNING: Any change of state to the Socket might hurt the execution of this library.  
        /// </remarks>
        public StreamSocket Socket { get; private set; }

        /// <summary>
        /// Gets the PeerInformation used for Mindwave.
        /// </summary>
        /// <remarks>
        /// WARNING: any attempt to connect to this peer directly might hurt the exceution of this library.
        /// </remarks>
        public PeerInformation PeerInformation { get; private set; }

        /// <summary>
        /// Gets a value indicating whether the bluetooth socket is active.
        /// </summary>
        public bool IsConnected
        {
            get
            {
                return State == MindwaveServiceState.ConnectedWithData
                       || State == MindwaveServiceState.ConnectedWithUnreliableData
                       || State == MindwaveServiceState.ConnectedWithNoDataYet;
            }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Stop();
        }

        /// <summary>
        /// Opens a bluetooth socket to a Mindwave Mobile headset and provide MindwaveSensorReadings. 
        /// </summary>
        public async void Start()
        {
            if (IsConnected)
            {
                throw new ApplicationException("Cannot Start a sensor once it's also started.");
            }
            State = MindwaveServiceState.Connecting;

            try
            {

                PeerFinder.AlternateIdentities["Bluetooth:Paired"] = "";

                var peers = await PeerFinder.FindAllPeersAsync();

                if (!peers.Any(p => p.DisplayName.Contains("MindWave")))
                {
                    State = MindwaveServiceState.FailedConnection;
                }
                else
                {
                    PeerInformation = peers.First(p => p.DisplayName.Contains("MindWave"));

                    Socket = new StreamSocket();
                    await Socket.ConnectAsync(PeerInformation.HostName, "1");

                    State = MindwaveServiceState.ConnectedWithNoDataYet;

                    try
                    {
                        InitalizeListeningToSocket();
                    }
                    catch
                    {
                        State = MindwaveServiceState.FailedDuringExecution;
                    }
                }
            }
            catch
            {
                State = MindwaveServiceState.FailedConnection;
            }
        }

        private async void InitalizeListeningToSocket()
        {
            while (IsConnected)
            {
                var resultArray = await GetNextBuffer();
                //Debug.WriteLine(string.Join(",", resultArray.Select(b => b.ToString())));

                int? indexOfUsefulDataHeader = GetUsefulDataHeaderIndex(resultArray);
                if (indexOfUsefulDataHeader.HasValue == false)
                {
                    // ignore data and just dump it
                }
                else
                {
                    // Check if enough data exists to finalize this useful data packet, if not, get another
                    if (indexOfUsefulDataHeader.Value + LengthOfUsefulPacket > resultArray.Length)
                    {
                        var nextResultsArray = await GetNextBuffer();
                        resultArray = resultArray.Concat(nextResultsArray).ToArray();
                    }

                    var usefulDataPacket =
                        resultArray
                            .Skip(indexOfUsefulDataHeader.Value)
                            .Take(LengthOfUsefulPacket + 4)
                            .ToArray();
                    //Print(string.Join(",", usefulDataPacket.Select(b => b.ToString())));

                    // based on http://wearcam.org/ece516/mindset_communications_protocol.pdf
                    var data = new MindwaveReading(
                        usefulDataPacket[4],
                        IntHelper.GetUnsignedInt24ValueFrom3Bytes(usefulDataPacket, 7),
                        IntHelper.GetUnsignedInt24ValueFrom3Bytes(usefulDataPacket, 10),
                        IntHelper.GetUnsignedInt24ValueFrom3Bytes(usefulDataPacket, 13),
                        IntHelper.GetUnsignedInt24ValueFrom3Bytes(usefulDataPacket, 16),
                        IntHelper.GetUnsignedInt24ValueFrom3Bytes(usefulDataPacket, 19),
                        IntHelper.GetUnsignedInt24ValueFrom3Bytes(usefulDataPacket, 22),
                        IntHelper.GetUnsignedInt24ValueFrom3Bytes(usefulDataPacket, 25),
                        IntHelper.GetUnsignedInt24ValueFrom3Bytes(usefulDataPacket, 28),
                        usefulDataPacket[32],
                        usefulDataPacket[34],
                        DateTime.Now);
                    //Print(data.ToString());
                    HandleNewMindwaveReading(data);
                }
            }

            if (!IsConnected)
            {
                Socket.Dispose();
            }
        }

        private const int ParserSync = 0xAA;
        private const int UsefulDataPacketLength = 32;
        private const int LengthOfUsefulPacket =
            /*Syncs Length*/ 2 +
            /* Packet Legnth */ 1 +
            UsefulDataPacketLength +
            /* checksum */ 1;

        private int? GetUsefulDataHeaderIndex(byte[] resultArray)
        {
            for (int i = 0; i < resultArray.Length - 2; i++)
            {
                if (resultArray[i] == ParserSync
                    && resultArray[i + 1] == ParserSync
                    && resultArray[i + 2] == UsefulDataPacketLength)
                {
                    return i;
                }
            }
            return null;
        }

        private async Task<byte[]> GetNextBuffer(uint length = 512)
        {
            var buffer = await Socket.InputStream.ReadAsync(new Windows.Storage.Streams.Buffer(length), length,
                                                            InputStreamOptions.None);

            var resultArray = buffer.GetBytesFromBuffer();
            return resultArray;
        }

        private void HandleNewMindwaveReading(MindwaveReading data)
        {
            State = data.IsDataReliable
                ? MindwaveServiceState.ConnectedWithData
                : MindwaveServiceState.ConnectedWithUnreliableData;
            CurrentValue = data;
        }

        /// <summary>
        /// Closes the open bluetooth socket to a Mindwave Mobile headset. 
        /// </summary>
        public void Stop()
        {
            State = MindwaveServiceState.NotConnected;
        }

        private MindwaveServiceState _state;

        /// <summary>
        /// Gets the current state of the Mindwave sensor. 
        /// </summary>
        public MindwaveServiceState State
        {
            get { return _state; }
            private set
            {
                var oldValue = _state;
                _state = value;
                if (oldValue != value)
                {
                    OnStateChanged(oldValue, value);
                }
            }
        }

        /// <summary>
        /// Raised when the state of the bluetooth connection or data retrival changes. 
        /// </summary>
        public event EventHandler<MindwaveStateChangedEventArgs> StateChanged;

        private void OnStateChanged(MindwaveServiceState previousState, MindwaveServiceState currentState)
        {
            EventHandler<MindwaveStateChangedEventArgs> handler = StateChanged;
            if (handler != null) handler(this, new MindwaveStateChangedEventArgs(previousState, currentState));
        }

        private MindwaveReading _currentValue;

        /// <summary>
        /// Gets the current reading from the Mindwave Mobile Headset. 
        /// </summary>
        public MindwaveReading CurrentValue
        {
            get
            {
                return _currentValue;
            }
            private set
            {
                var oldValue = _currentValue;
                _currentValue = value;
                if (oldValue != value)
                {
                    OnCurrentValueChanged(value);
                }
            }
        }

        /// <summary>
        /// Raised when a new MindwaveReading is available. 
        /// </summary>
        public event EventHandler<MindwaveReadingEventArgs> CurrentValueChanged;

        private void OnCurrentValueChanged(MindwaveReading reading)
        {
            EventHandler<MindwaveReadingEventArgs> handler = CurrentValueChanged;
            if (handler != null) handler(this, new MindwaveReadingEventArgs(reading));
        }

        /// <summary>
        /// Gets a value indicating whether the bluetooth socket is available and getting high quality data. 
        /// </summary>
        public bool IsDataValid
        {
            get { return State == MindwaveServiceState.ConnectedWithData; }
        }

        /// <summary>
        /// Gets or sets the time between sensor updates. 
        /// </summary>
        public TimeSpan TimeBetweenUpdates
        {
            get
            {
                // based on documentation. 
                return TimeSpan.FromMilliseconds(512);
            }
            set
            {
                throw new NotSupportedException("Time between updates is determined by the headset");
            }
        }
    }
}